/**
 * 
 */
package com.ftpclient;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.math.BigInteger;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.time.Duration;
import java.time.LocalDateTime;

import cn.hutool.core.date.DatePattern;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.date.LocalDateTimeUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.file.FileAppender;
import cn.hutool.core.io.file.FileReader;
import cn.hutool.core.io.file.FileWriter;
import cn.hutool.core.net.NetUtil;
import cn.hutool.core.util.CharsetUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.cron.CronUtil;
import cn.hutool.cron.task.Task;
import cn.hutool.extra.ftp.Ftp;
import cn.hutool.setting.Setting;

/**
 * @author w.weizhen
 *
 */
public class ClientDoor {
    private static final String user_dir = System.getProperty("user.dir")+File.separator;
    //读取classpath下的config.setting,不使用变量
    private static final Setting setting = new Setting(user_dir+"config.setting", CharsetUtil.CHARSET_UTF_8, true);
    private static final String name = setting.getStr("name");//设定的用户名
    private static final String password = setting.getStr("password");//设定的用户秘密
    private static final String local_file = setting.getStr("local_file");//本地文件所在目录的路径
    private static final String ip = setting.getStr("ip");//IP地址
    private static final Integer port = setting.getInt("port")!=null&&NetUtil.isValidPort(setting.getInt("port"))?setting.getInt("port"):21;//设定的端口号
    private static final String cron = setting.getStr("cron");//表达式,指定下载时间间隔
    private static final Boolean log = setting.getBool("log");//是否开启日志
    private static final Integer expire_logs_days = setting.getInt("expire_logs_days");//日志保存天数
    private static Ftp ftp = null;//ftp客户端
    private static long times = 0;//计算循环次数
    private static File tmpDirectory ;//临时文件目录
    /**
     * 程序入口
     */
    public static void main(String[] args) {
    	//首先去校验配置文件中的必须参数,如果参数缺失则不能启动应用。
        //只在启动的时候校验一次,
        if(!verifyConfig())
        {
            log("配置文件没有通过校验!程序未启动!");
            return;
        }
    	run();
    }
    
    private static void run()
    {
    	log("程序启动中!");
        try {
            tmpDirectory = new File(user_dir, "tmp"+File.separator);
    		if(!tmpDirectory.exists())
    		{
    			tmpDirectory.mkdir();
    		}
    		
            //定时更新文件
            CronUtil.schedule(cron, new Task() {
                @Override
                public void execute() {
                    try {
                    	ftp = new Ftp(ip, port, name, password);
                    	download();
					} catch (Exception e) {
						log(e.toString());
					}finally {
						try {
							ftp.close();
						} catch (IOException e) {
							log(e.toString());
						}
					}
                }
            });
            
            // 支持秒级别定时任务
            CronUtil.setMatchSecond(true);
            CronUtil.start();
        } catch (Exception e) {
		    log(e.toString());
        }
        log("程序已启动!");
    }
    
    //下载
    private static void download()
    {
    	log("开始更新本地文件! Start Run times=" + (times++));
		//递归下载FTP服务器上文件到本地(文件目录和服务器同步)[这里使用MD5校验文件,不能直接递归下载]
    	ftp.recursiveDownloadFolder("/", tmpDirectory);
    	File[] tmpFiles = tmpDirectory.listFiles();
    	for (int i = 0; i < tmpFiles.length; i++) 
    	{
    		File tmpFile = tmpFiles[i];
    		if(!tmpFile.exists()||!tmpFile.isFile())
    		{
    			continue;
    		}
    		
    		String file_name = tmpFile.getName();
    		//文件下载成功,本地文件是否存在,如果本地文件存在则对比两个文件,如果两个文件不同则覆盖本地文件。如果本地文件不存在,则将新文件复制到本地。
        	File remFile = new File(local_file, file_name);
        	//本地文件存在,
        	if(remFile.exists())
        	{
        		String tmpMD5 = getMD5(tmpFile);
        		String remMD5 = getMD5(remFile);
        		if(tmpMD5.equals(remMD5))
        		{
        			log("文件:"+file_name+",    \r\n 远程文件MD5:"+tmpMD5+", 本地文件MD5:"+remMD5+".    \r\n 文件MD5相同,不做覆盖! 结束更新这个文件!");
        			//删除临时文件
        			FileUtil.del(tmpFile);
        			continue;
        		}
        	}
        	//本地文件不存在或本地文件与服务器文件不同,执行文件覆盖。
        	FileReader fileReader = new FileReader(tmpFile);//读取临时文件
        	FileWriter writer = new FileWriter(remFile);//要写入的文件
        	writer.write(fileReader.readString());//要写入的文件
        	//删除临时文件
        	FileUtil.del(tmpFile);
        	log("更新文件:" + file_name);
		}
    	log("结束更新本地文件! End Run times=" + (times++));
    }
    
    // 校验参数是否全部在配置文件中
    private static boolean verifyConfig()
    {
        boolean verify = true;
        try {
            // 用户名为空
            if(StrUtil.isBlank(name))
            {
                log("配置文件config.settig中name[用户名]没有配置!");
                verify = false;
            }
            // 密码为空
            if(StrUtil.isBlank(password))
            {
                log("配置文件config.settig中password[密码]没有配置!");
                verify = false;
            }
            // 本地文件所在目录的路径 为空
            if(StrUtil.isBlank(local_file))
            {
                log("配置文件config.settig中local_file[本地文件所在目录的路径]没有配置!");
                verify = false;
            }
            else
            {
                //路径是否存在
                File file = new File(local_file);
                if(!file.exists()||!file.isDirectory())
                {
                    log("配置文件config.settig中local_file[本地文件所在目录的路径]不存在或不是一个文件夹!");
                    verify = false;
                }
            }
            // IP为空
            if(StrUtil.isBlank(ip))
            {
                log("配置文件config.settig中ip没有配置!");
                verify = false;
            }
            // cron为空
            if(StrUtil.isBlank(cron))
            {
                log("配置文件config.settig中download_cron[时间间隔]没有配置!");
                verify = false;
            }
        } catch (Exception e) {
            verify = false;
		    log(e.toString());
        }
        return verify;
    }
    
    // 写日志,这里不用组件,自己写一个简单的
    private static void log(String content)
    {
        //日志是否开启
        if (!log) {return;}
        //当前时间
        String now = DateUtil.now();
        content = now + "  " + content;
        //日志文件路径
        String path = user_dir+"log"+File.separator + DateUtil.today()+".log";
        //写入文件
        File file = new File(path);
        FileAppender appender = new FileAppender(file, 10, true);
        appender.append(content);
        appender.flush();
    }
    
    // 管理过期日志
    private static void expireLog()
    {
        log("开始管理过期日志!");
        try {
        	//日志文件路径
            File directory = new File(user_dir+"log");
            if(!directory.exists())
            {
                log("结束管理过期日志!日志目录不存在!");
                return;
            }
            File[] files = directory.listFiles();
            if (files==null||files.length<1) 
            {
                log("结束管理过期日志!没有日志文件!");
                return;
            }
            //当前时间
            String now = DateUtil.today();
            LocalDateTime end = LocalDateTimeUtil.parse(now, DatePattern.NORM_DATE_PATTERN);
            for (File file : files) 
            {
            	String name = file.getName();
                LocalDateTime start = LocalDateTimeUtil.parse(name.replaceAll(".log", ""), DatePattern.NORM_DATE_PATTERN);
                Duration between = LocalDateTimeUtil.between(start, end);
                if(between.toDays() >= expire_logs_days)
                {
                    file.delete();
                }
            }
		} catch (Exception e) {
		    log(e.toString());
		}
        log("结束管理过期日志!");
    }
    
    //获取文件MD5
    public static String getMD5(File file) {
        BigInteger bi = null;
        try {
            byte[] buffer = new byte[8192];
            int len = 0;
            MessageDigest md = MessageDigest.getInstance("MD5");
            FileInputStream fis = new FileInputStream(file);
            while ((len = fis.read(buffer)) != -1) {
                md.update(buffer, 0, len);
            }
            fis.close();
            byte[] b = md.digest();
            bi = new BigInteger(1, b);
        } catch (NoSuchAlgorithmException e) {
		    log(e.toString());
        } catch (IOException e) {
		    log(e.toString());
        }
        return bi.toString(16);
    }
}